
package com.trydofor.id.loader;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import javax.sql.DataSource;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.core.A9Loader;
import com.trydofor.id.core.Range;

/**
 * @author : Shi Rongjiu (www.trydofor.com)
 */
public final class PureJdbcRange extends PureJdbcLoader implements A9Loader<Range> {
    
    private static final String UPD_NULL;
    private static final String UPD_NEXT;
    private static final String SLT_RANGE;
    
    // init final
    static {
        SLT_RANGE = "SELECT LONG_START,LONG_ENDUP,LONG_CURRENT FROM LONG_RANGE WHERE LONG_MAKER=?";
        UPD_NULL = "UPDATE LONG_RANGE SET LONG_CURRENT=? WHERE LONG_CURRENT IS NULL AND LONG_MAKER=?";
        UPD_NEXT = "UPDATE LONG_RANGE SET LONG_CURRENT=? WHERE LONG_CURRENT=? AND LONG_MAKER=?";
    }
    
    /**
     * {@inheritDoc}
     */
    public PureJdbcRange(String mk, DataSource ds, int id) throws SQLException {
        super(mk, ds, id);
    }
    
    /**
     * {@inheritDoc}
     */
    public PureJdbcRange(int mk, DataSource ds, int id) {
        super(mk, ds, id);
    }
    
    @Override
    public Range heavyLoad() throws Exception {
        final Connection conn = dataSource.getConnection();
        final boolean autoCommit = conn.getAutoCommit();
        PreparedStatement pst = null;
        Savepoint savepoint = null;
        boolean empty = false;
        long start = 0, endup = 0;
        try {
            // select
            pst = conn.prepareStatement(SLT_RANGE);
            pst.setInt(1, makerId);
            ResultSet rs = pst.executeQuery();
            boolean got = false;
            while (rs.next()) {
                Exceptions.checkIllegalState(got, "got more than 1 record. maker=%s", makerId);
                long st = rs.getLong("LONG_START");
                long eu = rs.getLong("LONG_ENDUP");
                start = rs.getLong("LONG_CURRENT");
                if (rs.wasNull()) {
                    start = st;
                    empty = true;
                }
                if (eu >= st) {
                    endup = start + fetchCount;
                }
                else {
                    endup = start - fetchCount;
                }
                
                Range rn = new Range(st, eu);
                Exceptions.checkIllegalState(!rn.contains(start), "%s outof %s. maker=%s", start, rn, makerId);
                Exceptions.checkIllegalState(!rn.contains(endup), "%s outof %s. maker=%s", endup, rn, makerId);
                
                got = true;
            }
            Exceptions.checkIllegalState(!got, "can not find record. maker=%s", makerId);
            rs.close();
            pst.close();
            
            // update
            if (autoCommit) {
                conn.setAutoCommit(false);
            }
            savepoint = conn.setSavepoint();
            if (empty) {
                pst = conn.prepareStatement(UPD_NULL);
                pst.setLong(1, endup);
                pst.setLong(2, makerId);
            }
            else {
                pst = conn.prepareStatement(UPD_NEXT);
                pst.setLong(1, endup);
                pst.setLong(2, start);
                pst.setLong(3, makerId);
            }
            
            int rc = pst.executeUpdate();
            Exceptions.checkIllegalState(rc != 1, "%s records affected. makerId=%s", rc, makerId);
        }
        catch (Exception e) {
            if (savepoint != null) {
                conn.rollback(savepoint);
            }
            throw e;
        }
        finally {
            if (autoCommit) {
                conn.setAutoCommit(true);
            }
            if (pst != null) {
                pst.close();
            }
        }
        return new Range(start, true, endup, false);
    }
    
    @Override
    public void handleException(Exception e) {
        Exceptions.throwFakeException(e);
    }
}
